/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
#include <cutils/log.h>
#include <jni.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "../include/pinyinime.h"
#include "../include/sync.h"
#include "../include/userdict.h"

#ifdef __cplusplus
extern "C" {
#endif

    using namespace ime_pinyin;

#define RET_BUF_LEN 256

    static char16 retbuf[RET_BUF_LEN];
    static char16 ( *predict_buf ) [kMaxPredictSize + 1] = NULL;
    static Size_t predict_len;

    static Sync sync_worker;

    static struct file_descriptor_offsets_t {
        jclass mClass;
        jfieldID mDescriptor;
    } gFileDescriptorOffsets;

    JNIEXPORT jboolean JNICALL nativeImOpenDecoder ( JNIEnv *env, jclass jclazz,
                                                     jbyteArray fn_sys_dict,
                                                     jbyteArray fn_usr_dict ) {
        jbyte *fsd = ( *env ).GetByteArrayElements ( fn_sys_dict, 0 );
        jbyte *fud = ( *env ).GetByteArrayElements ( fn_usr_dict, 0 );
        jboolean jret = JNI_FALSE;
        if ( im_open_decoder ( ( const char * ) fsd, ( const char * ) fud ) )
        { jret = JNI_TRUE; }
        ( *env ).ReleaseByteArrayElements ( fn_sys_dict, fsd, 0 );
        ( *env ).ReleaseByteArrayElements ( fn_usr_dict, fud, 0 );
        return jret;
    }

    JNIEXPORT jboolean JNICALL nativeImOpenDecoderFd ( JNIEnv *env, jclass jclazz,
                                                       jobject fd_sys_dict,
                                                       jlong startoffset,
                                                       jlong length,
                                                       jbyteArray fn_usr_dict ) {
        jint fd = env->GetIntField ( fd_sys_dict, gFileDescriptorOffsets.mDescriptor );
        jbyte *fud = ( *env ).GetByteArrayElements ( fn_usr_dict, 0 );
        jboolean jret = JNI_FALSE;
        int newfd = dup ( fd );
        if ( im_open_decoder_fd ( newfd, startoffset, length, ( const char * ) fud ) )
        { jret = JNI_TRUE; }
        close ( newfd );
        ( *env ).ReleaseByteArrayElements ( fn_usr_dict, fud, 0 );
        return jret;
    }

    JNIEXPORT void JNICALL nativeImSetMaxLens ( JNIEnv *env, jclass jclazz,
                                                jint max_sps_len,
                                                jint max_hzs_len ) {
        im_set_max_lens ( static_cast<Size_t> ( max_sps_len ),
                          static_cast<Size_t> ( max_hzs_len ) );
        return;
    }

    JNIEXPORT jboolean JNICALL nativeImCloseDecoder ( JNIEnv *env, jclass jclazz ) {
        im_close_decoder();
        return JNI_TRUE;
    }

    JNIEXPORT jint JNICALL nativeImSearch ( JNIEnv *env, jclass jclazz,
                                            jbyteArray pybuf, jint pylen ) {
        jbyte *array_body = ( *env ).GetByteArrayElements ( pybuf, 0 );
        jint jret = 0;
        if ( NULL != array_body ) {
            jret = im_search ( ( const char * ) array_body, pylen );
        }
        ( *env ).ReleaseByteArrayElements ( pybuf, array_body, 0 );
        return jret;
    }

    JNIEXPORT jint JNICALL nativeImDelSearch ( JNIEnv *env, jclass jclazz, jint pos,
                                               jboolean is_pos_in_splid,
                                               jboolean clear_fixed_this_step ) {
        return im_delsearch ( pos, is_pos_in_splid, clear_fixed_this_step );
    }

    JNIEXPORT void JNICALL nativeImResetSearch ( JNIEnv *env, jclass jclazz ) {
        im_reset_search();
        return;
    }

    JNIEXPORT jint JNICALL nativeImAddLetter ( JNIEnv *env, jclass clazz, jbyte ch ) {
        return im_add_letter ( ch );
    }

    JNIEXPORT jstring JNICALL nativeImGetPyStr ( JNIEnv *env, jclass jclazz,
                                                 jboolean decoded ) {
        Size_t py_len;
        const char *py = im_get_sps_str ( &py_len ); // py_len gets decoded length
        assert ( NULL != py );
        if ( !decoded )
        { py_len = strlen ( py ); }
        const unsigned short *spl_start;
        Size_t len;
        len = im_get_spl_start_pos ( spl_start );
        Size_t i;
        for ( i = 0; i < py_len; i++ )
        { retbuf[i] = py[i]; }
        retbuf[i] = ( char16 ) '\0';
        jstring retstr = ( *env ).NewString ( ( unsigned short * ) retbuf, i );
        return retstr;
    }

    JNIEXPORT jint JNICALL nativeImGetPyStrLen ( JNIEnv *env, jclass jclazz,
                                                 jboolean decoded ) {
        Size_t py_len;
        const char *py = im_get_sps_str ( &py_len ); // py_len gets decoded length
        assert ( NULL != py );
        if ( !decoded )
        { py_len = strlen ( py ); }
        return py_len;
    }

    JNIEXPORT jintArray JNICALL nativeImGetSplStart ( JNIEnv *env, jclass jclazz ) {
        const unsigned short *spl_start;
        Size_t len;
        // There will be len + 1 elements in the buffer when len > 0.
        len = im_get_spl_start_pos ( spl_start );
        jintArray arr = ( *env ).NewIntArray ( len + 2 );
        jint *arr_body = ( *env ).GetIntArrayElements ( arr, 0 );
        assert ( NULL != arr_body );
        arr_body[0] = len; // element 0 is used to store the length of buffer.
        for ( Size_t i = 0; i <= len; i++ )
        { arr_body[i + 1] = spl_start[i]; }
        ( *env ).ReleaseIntArrayElements ( arr, arr_body, 0 );
        return arr;
    }

    JNIEXPORT jstring JNICALL nativeImGetChoice ( JNIEnv *env, jclass clazz,
                                                  jint candidateId ) {
        jstring retstr;
        if ( im_get_candidate ( candidateId, retbuf, RET_BUF_LEN ) ) {
            retstr = ( *env ).NewString ( retbuf, utf16_strlen ( retbuf ) );
            return retstr;
        } else {
            retstr = ( *env ).NewString ( ( unsigned short * ) retbuf, 0 );
            return retstr;
        }
    }

    JNIEXPORT jint JNICALL nativeImChoose ( JNIEnv *env, jclass clazz,
                                            jint choice_id ) {
        return im_choose ( choice_id );
    }

    JNIEXPORT jint JNICALL nativeImCancelLastChoice ( JNIEnv *env, jclass clazz ) {
        return im_cancel_last_choice();
    }

    JNIEXPORT jint JNICALL nativeImGetFixedLen ( JNIEnv *env, jclass clazz ) {
        return im_get_fixed_len();
    }

    JNIEXPORT jboolean JNICALL nativeImCancelInput ( JNIEnv *env, jclass clazz ) {
        if ( im_cancel_input() )
        { return JNI_TRUE; }
        return JNI_FALSE;
    }

    JNIEXPORT jboolean JNICALL nativeImFlushCache ( JNIEnv *env, jclass clazz ) {
        im_flush_cache();
        return JNI_TRUE;
    }

    JNIEXPORT jint JNICALL nativeImGetPredictsNum ( JNIEnv *env, jclass clazz,
                                                    jstring fixed_str ) {
        char16 *fixed_ptr = ( char16 * ) ( *env ).GetStringChars ( fixed_str, NULL );
        Size_t fixed_len = ( Size_t ) ( *env ).GetStringLength ( fixed_str );
        char16 fixed_buf[kMaxPredictSize + 1];
        if ( fixed_len > kMaxPredictSize ) {
            fixed_ptr += fixed_len - kMaxPredictSize;
            fixed_len = kMaxPredictSize;
        }
        utf16_strncpy ( fixed_buf, fixed_ptr, fixed_len );
        fixed_buf[fixed_len] = ( char16 ) '\0';
        predict_len = im_get_predicts ( fixed_buf, predict_buf );
        ( *env ).ReleaseStringChars ( fixed_str, fixed_ptr );
        return predict_len;
    }

    JNIEXPORT jstring JNICALL nativeImGetPredictItem ( JNIEnv *env, jclass clazz,
                                                       jint predict_no ) {
        jstring retstr;
        if ( predict_no < 0 || ( Size_t ) predict_no >= predict_len ) {
            retstr = ( *env ).NewString ( ( unsigned short * ) predict_buf[0], 0 );
        } else {
            retstr = ( *env ).NewString ( ( unsigned short * ) predict_buf[predict_no],
                                          utf16_strlen ( predict_buf[predict_no] ) );
        }
        return retstr;
    }

    JNIEXPORT jboolean JNICALL nativeSyncBegin ( JNIEnv *env, jclass clazz,
                                                 jbyteArray dict_file ) {
        jbyte *file_name = ( *env ).GetByteArrayElements ( dict_file, 0 );
        jboolean jret = JNI_FALSE;
        if ( true == sync_worker.begin ( ( const char * ) file_name ) )
        { jret = JNI_TRUE; }
        ( *env ).ReleaseByteArrayElements ( dict_file, file_name, 0 );
        return jret;
    }

    JNIEXPORT jboolean JNICALL nativeSyncFinish ( JNIEnv *env, jclass clazz ) {
        sync_worker.finish();
        return JNI_TRUE;
    }

    JNIEXPORT jint JNICALL nativeSyncGetCapacity ( JNIEnv *env, jclass clazz ) {
        return sync_worker.get_capacity();
    }

    JNIEXPORT jint JNICALL nativeSyncPutLemmas ( JNIEnv *env, jclass clazz,
                                                 jstring tomerge ) {
        char16 *ptr = ( char16 * ) ( *env ).GetStringChars ( tomerge, NULL );
        int len = ( Size_t ) ( *env ).GetStringLength ( tomerge );
        int added = sync_worker.put_lemmas ( ptr, len );
        ( *env ).ReleaseStringChars ( tomerge, ptr );
        return added;
    }

    JNIEXPORT jstring JNICALL nativeSyncGetLemmas ( JNIEnv *env, jclass clazz ) {
        int len = sync_worker.get_lemmas ( retbuf, RET_BUF_LEN );
        if ( len == 0 )
        { return NULL; }
        jstring retstr;
        retstr = ( *env ).NewString ( ( unsigned short * ) retbuf, len );
        return retstr;
    }

    JNIEXPORT jint JNICALL nativeSyncGetLastCount ( JNIEnv *env, jclass clazz ) {
        return sync_worker.get_last_got_count();
    }

    JNIEXPORT jint JNICALL nativeSyncGetTotalCount ( JNIEnv *env, jclass clazz ) {
        return sync_worker.get_total_count();
    }

    JNIEXPORT jboolean JNICALL nativeSyncClearLastGot ( JNIEnv *env, jclass clazz ) {
        sync_worker.clear_last_got();
        return JNI_TRUE;
    }

    /**
     * Table of methods associated with a single class.
     */
    static JNINativeMethod gMethods[] = {
        /* name, signature, funcPtr */
        /* ------Functions for Pinyin-to-hanzi decoding begin--------->> */
        {
            "nativeImOpenDecoder", "([B[B)Z",
            ( void * ) nativeImOpenDecoder
        },
        {
            "nativeImOpenDecoderFd", "(Ljava/io/FileDescriptor;JJ[B)Z",
            ( void * ) nativeImOpenDecoderFd
        },
        {
            "nativeImSetMaxLens", "(II)V",
            ( void * ) nativeImSetMaxLens
        },
        {
            "nativeImCloseDecoder", "()Z",
            ( void * ) nativeImCloseDecoder
        },
        {
            "nativeImSearch",  "([BI)I",
            ( void * ) nativeImSearch
        },
        {
            "nativeImDelSearch",  "(IZZ)I",
            ( void * ) nativeImDelSearch
        },
        {
            "nativeImResetSearch",  "()V",
            ( void * ) nativeImResetSearch
        },
        {
            "nativeImAddLetter", "(B)I",
            ( void * ) nativeImAddLetter
        },
        {
            "nativeImGetPyStr", "(Z)Ljava/lang/String;",
            ( void * ) nativeImGetPyStr
        },
        {
            "nativeImGetPyStrLen", "(Z)I",
            ( void * ) nativeImGetPyStrLen
        },
        {
            "nativeImGetSplStart", "()[I",
            ( void * ) nativeImGetSplStart
        },
        {
            "nativeImGetChoice", "(I)Ljava/lang/String;",
            ( void * ) nativeImGetChoice
        },
        {
            "nativeImChoose", "(I)I",
            ( void * ) nativeImChoose
        },
        {
            "nativeImCancelLastChoice", "()I",
            ( void * ) nativeImCancelLastChoice
        },
        {
            "nativeImGetFixedLen", "()I",
            ( void * ) nativeImGetFixedLen
        },
        {
            "nativeImGetPredictsNum", "(Ljava/lang/String;)I",
            ( void * ) nativeImGetPredictsNum
        },
        {
            "nativeImGetPredictItem", "(I)Ljava/lang/String;",
            ( void * ) nativeImGetPredictItem
        },
        {
            "nativeImCancelInput", "()Z",
            ( void * ) nativeImCancelInput
        },
        {
            "nativeImFlushCache", "()Z",
            ( void * ) nativeImFlushCache
        },
        /* <<----Functions for Pinyin-to-hanzi decoding end------------- */

        /* ------Functions for sync begin----------------------------->> */
        {
            "nativeSyncBegin", "([B)Z",
            ( void * ) nativeSyncBegin
        },
        {
            "nativeSyncFinish", "()Z",
            ( void * ) nativeSyncFinish
        },
        {
            "nativeSyncPutLemmas", "(Ljava/lang/String;)I",
            ( void * ) nativeSyncPutLemmas
        },
        {
            "nativeSyncGetLemmas", "()Ljava/lang/String;",
            ( void * ) nativeSyncGetLemmas
        },
        {
            "nativeSyncGetLastCount", "()I",
            ( void * ) nativeSyncGetLastCount
        },
        {
            "nativeSyncGetTotalCount", "()I",
            ( void * ) nativeSyncGetTotalCount
        },
        {
            "nativeSyncClearLastGot", "()Z",
            ( void * ) nativeSyncClearLastGot
        },
        {
            "nativeSyncGetCapacity", "()I",
            ( void * ) nativeSyncGetCapacity
        },
        /* <<----Functions for sync end--------------------------------- */
    };


    /*
     * Register several native methods for one class.
     */
    static int registerNativeMethods ( JNIEnv *env, const char *className,
                                       JNINativeMethod *gMethods, int numMethods ) {
        jclass clazz;
        clazz = ( *env ).FindClass ( className );
        if ( clazz == NULL ) {
            return JNI_FALSE;
        }
        if ( ( *env ).RegisterNatives ( clazz, gMethods, numMethods ) < 0 ) {
            return JNI_FALSE;
        }
        clazz = env->FindClass ( "java/io/FileDescriptor" );
        LOG_FATAL_IF ( clazz == NULL, "Unable to find Java class java.io.FileDescriptor" );
        gFileDescriptorOffsets.mClass = ( jclass ) env->NewGlobalRef ( clazz );
        gFileDescriptorOffsets.mDescriptor = env->GetFieldID ( clazz, "descriptor", "I" );
        LOG_FATAL_IF ( gFileDescriptorOffsets.mDescriptor == NULL,
                       "Unable to find descriptor field in java.io.FileDescriptor" );
        return JNI_TRUE;
    }

    /*
     * Register native methods for all classes we know about.
     */
    static int registerNatives ( JNIEnv *env ) {
        if ( !registerNativeMethods ( env,
                                      "com/droidlogic/inputmethod/remote/PinyinDecoderService",
                                      gMethods, sizeof ( gMethods ) / sizeof ( gMethods[0] ) ) )
        { return JNI_FALSE; }
        return JNI_TRUE;
    }

    /*
     * Set some test stuff up.
     *
     * Returns the JNI version on success, -1 on failure.
     */
    JNIEXPORT jint JNICALL JNI_OnLoad ( JavaVM *vm, void *reserved ) {
        JNIEnv *env = NULL;
        jint result = -1;
        if ( ( *vm ).GetEnv ( ( void ** ) &env, JNI_VERSION_1_4 ) != JNI_OK ) {
            goto bail;
        }
        assert ( env != NULL );
        if ( !registerNatives ( env ) ) {
            goto bail;
        }
        /* success -- return valid version number */
        result = JNI_VERSION_1_4;
    bail:
        return result;
    }

#ifdef __cplusplus
}
#endif
